/* 
exTabSettings is Copyright 2007-2008 Daniel Remenak and distributed under the
terms of the GNU LGPL.

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

namespace exTabSettings
{
	using System;
	using Extensibility;
	using System.Runtime.InteropServices;
	using EnvDTE;

	#region Read me for Add-in installation and setup information.
	// When run, the Add-in wizard prepared the registry for the Add-in.
	// At a later time, if the Add-in becomes unavailable for reasons such as:
	//   1) You moved this project to a computer other than which is was originally created on.
	//   2) You chose 'Yes' when presented with a message asking if you wish to remove the Add-in.
	//   3) Registry corruption.
	// you will need to re-register the Add-in by building the exTabSettingsSetup project 
	// by right clicking the project in the Solution Explorer, then choosing install.
	#endregion
	
	/// <summary>
	///   The object for implementing an Add-in.
	/// </summary>
	/// <seealso class='IDTExtensibility2' />
	[GuidAttribute("4F89574D-F3CD-4458-BBD1-5DDAB2792976"), ProgId("exTabSettings.Connect")]
	public class Connect : Object, Extensibility.IDTExtensibility2
	{
		/***************************
		* Default values for indent
		* 
		* Change these at will.
		* They will be used whenever there is NO ex: line to be found
		***************************/
		private const int DefTabSize = 4;
		private const int DefIndentSize = 4;

		private EnvDTE.WindowEvents windowsEvents;
		private _DTE applicationObject;
		private AddIn addInInstance;

		/// <summary>
		///		Implements the constructor for the Add-in object.
		///		Place your initialization code within this method.
		/// </summary>
		public Connect()
		{
		}
		
		#region IDTExtensibility2 interface implementation
		/// <summary>
		///      Implements the OnConnection method of the IDTExtensibility2 interface.
		///      Receives notification that the Add-in is being loaded.
		/// </summary>
		/// <param term='application'>
		///      Root object of the host application.
		/// </param>
		/// <param term='connectMode'>
		///      Describes how the Add-in is being loaded.
		/// </param>
		/// <param term='addInInst'>
		///      Object representing this Add-in.
		/// </param>
		/// <seealso class='IDTExtensibility2' />
		public void OnConnection(object application, Extensibility.ext_ConnectMode connectMode, object addInInst, ref System.Array custom)
		{
			applicationObject = (_DTE)application;
			addInInstance = (AddIn)addInInst;

			EnvDTE.Events events = applicationObject.Events;
			windowsEvents = (EnvDTE.WindowEvents)events.get_WindowEvents(null);
			windowsEvents.WindowActivated += new _dispWindowEvents_WindowActivatedEventHandler(this.WindowActivated);
		}

		/// <summary>
		///     Implements the OnDisconnection method of the IDTExtensibility2 interface.
		///     Receives notification that the Add-in is being unloaded.
		/// </summary>
		/// <param term='disconnectMode'>
		///      Describes how the Add-in is being unloaded.
		/// </param>
		/// <param term='custom'>
		///      Array of parameters that are host application specific.
		/// </param>
		/// <seealso class='IDTExtensibility2' />
		public void OnDisconnection(Extensibility.ext_DisconnectMode disconnectMode, ref System.Array custom)
		{
			if(windowsEvents != null)
			{
				windowsEvents.WindowActivated -= new _dispWindowEvents_WindowActivatedEventHandler(this.WindowActivated);
			}
		}

		/// <summary>
		///      Implements the OnAddInsUpdate method of the IDTExtensibility2 interface.
		///      Receives notification that the collection of Add-ins has changed.
		/// </summary>
		/// <param term='custom'>
		///      Array of parameters that are host application specific.
		/// </param>
		/// <seealso class='IDTExtensibility2' />
		public void OnAddInsUpdate(ref System.Array custom)
		{
		}

		/// <summary>
		///      Implements the OnStartupComplete method of the IDTExtensibility2 interface.
		///      Receives notification that the host application has completed loading.
		/// </summary>
		/// <param term='custom'>
		///      Array of parameters that are host application specific.
		/// </param>
		/// <seealso class='IDTExtensibility2' />
		public void OnStartupComplete(ref System.Array custom)
		{
		}

		/// <summary>
		///      Implements the OnBeginShutdown method of the IDTExtensibility2 interface.
		///      Receives notification that the host application is being unloaded.
		/// </summary>
		/// <param term='custom'>
		///      Array of parameters that are host application specific.
		/// </param>
		/// <seealso class='IDTExtensibility2' />
		public void OnBeginShutdown(ref System.Array custom)
		{
		}
		#endregion

		/// <summary>
		///      Window activation event.
		///      Called when windows (tabs) are switched; scan the new window for modelines
		///      and set the indent settings appropriately.
		/// </summary>
		/// <param term='gotFocus'>
		///      Window which recieved focus
		/// </param>
		/// <param term='lostFocus'>
		///      Window which lost focus
		/// </param>
		public void WindowActivated(EnvDTE.Window gotFocus, EnvDTE.Window lostFocus)
		{
			// grab the last line with text
			if (gotFocus.Document != null && gotFocus.Document.Type == "Text")
			{
				TextDocument TD = gotFocus.Document.Object("") as TextDocument;
				if (TD == null) return;

				// check the last line, or the one before it if the last one is blank
				// FIXME: real modelines can be anywhere within the first or last five lines of the file
				TextPoint ep = TD.EndPoint;
				string lastline;
				if (ep.LineLength != 0)
					lastline = TD.CreateEditPoint(ep).GetLines(ep.Line, ep.Line + 1);
				else
					lastline = TD.CreateEditPoint(ep).GetLines(ep.Line - 1, ep.Line);

				// does it look like a vi:set or ex: line?
				if (lastline.Length != 0 && (lastline.IndexOf("ex:") != -1 || lastline.IndexOf("vi:set") != -1))
				{
					// shiftwidth
					int shiftwidth = GetIntValue(lastline, "shiftwidth");
					if (shiftwidth > 0 && shiftwidth < 16) // be reasonable
						SetDTEProperty("TextEditor", gotFocus.Document.Language, "IndentSize", shiftwidth);
					else
						SetDTEProperty("TextEditor", gotFocus.Document.Language, "IndentSize", DefIndentSize);

					// tabstop
					int tabstop = GetIntValue(lastline, "tabstop");
					if (tabstop > 0 && tabstop < 16) // be reasonable
						SetDTEProperty("TextEditor", gotFocus.Document.Language, "TabSize", tabstop);
					else
						SetDTEProperty("TextEditor", gotFocus.Document.Language, "TabSize", DefTabSize);

					// expandtab
					if (lastline.Contains("expandtab"))
						SetDTEProperty("TextEditor", gotFocus.Document.Language, "InsertTabs", false);
					else
						SetDTEProperty("TextEditor", gotFocus.Document.Language, "InsertTabs", true);
				}
				else
				{
					// use defaults
					SetDTEProperty("TextEditor", gotFocus.Document.Language, "IndentSize", DefIndentSize);
					SetDTEProperty("TextEditor", gotFocus.Document.Language, "TabSize", DefTabSize);
					SetDTEProperty("TextEditor", gotFocus.Document.Language, "InsertTabs", true);
				}
			}
		}

		/// <summary>
		///      Set a DTE property.
		/// </summary>
		/// <param term='Category'>
		///      The property category.
		/// </param>
		/// <param term='Page'>
		///      The property page.
		/// </param>
		/// <param term='Item'>
		///      The property item name.
		/// </param>
		/// <param term='Value'>
		///      The value to which to set the property.
		/// </param>
		/// <seealso class='IDTExtensibility2' />
		private void SetDTEProperty(string Category, string Page, string Item, object Value)
		{
			EnvDTE.Properties props = applicationObject.get_Properties( Category, Page);
			EnvDTE.Property prop = props.Item(Item);
			prop.Value = Value;
		}

		/// <summary>
		///      Grab an integer value from a modeline.
		///      We're pretty strict about format ("variable=NN").
		///      Returns -1 if the variable is not found on the modeline.
		/// </summary>
		/// <param term='line'>
		///      The vi or ex modeline which should be scanned.
		/// </param>
		/// <param term='key'>
		///      The name of the variable for which to scan.
		/// </param>
		/// <seealso class='IDTExtensibility2' />
		private int GetIntValue(string line, string key)
		{
			int index = line.IndexOf(key + "=") + (key.Length + 1);
			if (index != -1) 
			{
				// 4 is arbitrary, not magic...just "enough to get the number"
				string scan = line.Substring(index, (4 > line.Length - index) ? line.Length - index : 4 );
				int scanstop = scan.IndexOf(" ");
				if (scanstop != -1) scan = scan.Substring(0, scanstop);
				return int.Parse(scan);
			}
			else
			{
				return -1;	// not found
			}
		}
	}
}